﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using MegavaniaX.Messaging.Events;

namespace MegavaniaX
{
    /// <summary>
    /// An animation for the Megavania engine.
    /// </summary>
    public class Animation : ICloneable, IEventDispatcher
    {
        /// <summary>
        /// A messaging helper instance.
        /// </summary>
        private MessagingHelper messagingHelper;

        /// <summary>
        /// The current time of the animation.
        /// </summary>
        private TimeSpan animationTime;

        /// <summary>
        /// The list of animations.
        /// </summary>
        private List<AnimationEntry> animations;

        /// <summary>
        /// The current animation entry in the animations list.
        /// </summary>
        private int currentIndex;

        /// <summary>
        /// Boolean as to whether the animation loops.
        /// </summary>
        private bool doLoop;

        /// <summary>
        /// Default constructor, for creating totally new animations.
        /// </summary>
        public Animation() : this(null, true) { }

        /// <summary>
        /// Constructor that takes an existing list of animation entries.
        /// </summary>
        /// <param name="animationEntries">List of animation entries to use.</param>
        public Animation(List<AnimationEntry> animationEntries, bool loop)
        {
            animationTime = TimeSpan.Zero;
            if (animationEntries != null)
            {
                animations = animationEntries;
            }
            else
            {
                animations = new List<AnimationEntry>();
            }
            currentIndex = 0;
            doLoop = loop;
            messagingHelper = new MessagingHelper();
        }


        /// <summary>
        /// Adds a new animation entry to this animation.
        /// </summary>
        /// <param name="newEntry">Entry to add to the animation.</param>
        public void AddAnimationEntry(AnimationEntry newEntry)
        {
            animations.Add(newEntry);
        }

        /// <summary>
        /// Returns the current sprite of this animation.
        /// </summary>
        /// <returns>The current sprite of the animation.</returns>
        public MegavaniaSprite CurrentSprite()
        {
            return animations.ElementAt<AnimationEntry>(currentIndex).sprite;
        }

        /// <summary>
        /// Updates the animation.
        /// </summary>
        public void Update(TimeSpan elapsedTime)
        {
            // Add the time elapsed since the last update
            animationTime += elapsedTime;
            // If we've passed our duration move the index up
            if (animations.ElementAt<AnimationEntry>(currentIndex).duration != TimeSpan.Zero && animationTime >= animations.ElementAt<AnimationEntry>(currentIndex).duration)
            {
                TimeSpan difference = animationTime - animations.ElementAt<AnimationEntry>(currentIndex).duration;
                animationTime = difference;
                if (currentIndex <= animations.Count - 2)
                {
                    currentIndex++;
                }
                else if (animations.Count == currentIndex + 1 && doLoop)
                {
                    currentIndex = 0;
                }
                else
                {
                    DispatchEvent(new AnimationEvent(MegavaniaEventType.AnimationComplete));
                }
            }
        }

        /// <summary>
        /// Resets the animation.
        /// </summary>
        public void Reset()
        {
            animationTime = TimeSpan.Zero;
            currentIndex = 0;
        }

        /// <summary>
        /// Clones the animation.
        /// </summary>
        /// <returns>A deep copy of this animation.</returns>
        public object Clone()
        {
            Animation cloned = new Animation();
            // Clone the AnimationEntry list
            this.animations.ForEach((item) =>
                {
                    cloned.animations.Add(new AnimationEntry(item));
                }
                );
            cloned.animationTime = TimeSpan.Zero;
            cloned.currentIndex = 0;
            cloned.doLoop = this.doLoop;            
            return cloned;
        }

        #region IEventDispatcher

        /// <summary>
        /// Subscribes the given listener to events of type <typeparamref name="MegavaniaEventType"/>.
        /// </summary>
        /// <param name=param name="callback">The callback that will be executed.</param>
        /// <param name="type">The type of event to listen to.</param>
        public void Subscribe(MegavaniaEventCallback callback, MegavaniaEventType type, string name)
        {
            messagingHelper.Subscribe(callback, type, name);
        }

        /// <summary>
        /// Dispatches an event to any subscribed listeners.
        /// </summary>
        /// <param name="evt">The event to dispatch.</param>
        public void DispatchEvent(IMegavaniaEvent evt)
        {
            messagingHelper.DispatchEvent(evt);
        }


        #endregion


        #region AnimationEntry

        /// <summary>
        /// An animation entry in the Megavania game.
        /// </summary>
        public struct AnimationEntry
        {
            /// <summary>
            /// Constructs a new animation entry with the given parameters.
            /// </summary>
            /// <param name="sprite">The sprite to use.</param>
            /// <param name="duration">The duration of this sprite in the animation.</param>
            public AnimationEntry(MegavaniaSprite sprite, TimeSpan duration)
            {
                this.sprite = sprite;
                this.duration = duration;
            }

            /// <summary>
            /// Copy constructor for the animation entry.
            /// </summary>
            /// <param name="other">Animation entry to copy.</param>
            public AnimationEntry(AnimationEntry other)
            {
                this.sprite = other.sprite;
                this.duration = other.duration;
            }


            /// <summary>
            /// The sprite to display for this animation entry.
            /// </summary>
            public MegavaniaSprite sprite;

            /// <summary>
            /// The duration of this animation entry.
            /// </summary>
            public TimeSpan duration;
        }

        #endregion
    }
}
